﻿using Dapper;
using Oracle.ManagedDataAccess.Client;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;

namespace NetDh.DbUtility
{
    public class OracleHandle : DbHandleBase
    {
        #region 初始化必须
        /// <summary>
        ///初始化，需要传入连接字符串
        /// </summary>
        /// <param name="conStr">连接字符串</param>
        /// <returns></returns>
        public OracleHandle(string conStr) : base(conStr)
        {
            //DapperExtensions默认是用SqlServer的标记，这边使用Oracle的特殊语法标记，需要在初始化时给予指定。
            //DapperExtensions是静态的，全局只能支持一种数据库 
            DapperExtensions.DapperExtensions.SqlDialect = new DapperExtensions.Sql.OracleDialect();
        }

        /// <summary>
        /// 创建连接对象。子类需要重写，根据自己的数据库类型实例化自己的数据库连接对象(支持IDbConnection接口)。
        /// Dapper可以在所有Ado.net Providers下工作，包括sqlite, sqlce, firebird, oracle, MySQL, PostgreSQL and SQL Server。
        /// </summary>
        /// <returns></returns>
        protected override IDbConnection CreateConnection()
        {
            IDbConnection connection = new OracleConnection(ConnectionString);//Oracle.ManagedDataAccess.Client
            return connection;
        }
        #endregion

        #region oracle版的ExecuteDataSet
        /// <summary>
        /// oracle版。执行sql语句，并返回DataSet。为了简化写法，支持oracle一条sql有多条语句，用;隔开。
        /// (oracle一条sql不能执行多个语句，需要特殊处理，现在方案：一个连接多个DataReader)
        /// </summary>
        /// <param name="sql">sql如有多条语句，则用;隔开</param>
        /// <param name="param">匿名对象的参数，可以简单写，比如 new {Name="user1"} 即可，会统一处理参数和参数的size</param>
        public override DataSet ExecuteDataSet(string sql, dynamic param = null, int? cmdTimeout = DEFAULTTIMEOUT, IDbTransaction tran = null, CommandType? cmdType = null)
        {
            sql = CheckSql(sql);
            var sqls = sql.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            var conn = CreateConnectionAndOpen();
            try
            {
                DataSet dataSet = new DataSet();
                foreach (var singleSql in sqls)
                {
                    if (string.IsNullOrWhiteSpace(singleSql)) continue;
                    //经测试，Odp.Net中OracleDataAdapter.Fill第一次调用有性能问题，第二次就正常。而SqlServer的SqlDataAdapter.Fill无此问题
                    //这边用Dapper的ExecuteReader，即解决OracleDataAdapter.Fill的问题，又统一了函数参数写法，不用使用OracleParameter。
                    using (var reader = conn.ExecuteReader(singleSql, (object)param, tran, cmdTimeout, cmdType))
                    {
                        var dataTable = DataReaderToDataTable(reader);
                        dataSet.Tables.Add(dataTable);
                    }
                }
                return dataSet;
            }
            finally
            {
                conn.Close();
            }
        }
        #endregion

        #region 如果你的系统会使用不同数据库类型，或者今后会更改数据库类型，则建议重写的以下相关方法。否则，忽视之。
        /// <summary>
        /// oracle版。获取分页查询sql，支持多语句查询，详见otherSql参数
        /// (这边只负责组合分页sql)
        /// </summary>
        /// <param name="selectSql">待分页的主体sql，不可包含order by。e.g. select * from Ta A Inner Join Tb B On A.Id=B.UserId where A.Id=100 </param>
        /// <param name="orderSql">分页依据的排序，order by在这边传入。e.g. order by A.Id</param>
        /// <param name="startPageIndex">起始页，第1页从0开始</param>
        /// <param name="resultsPerPage">每页记录数</param>
        /// <param name="otherSql">同时要执行的其它sql语句。比如需要同时返回表记录总数，可在这边传入" select count(1) from TbUser"。
        /// 其实就是支持多条sql执行，比如也可以传:select count(1) from TbUser;select * from TbOrder。即多条sql用分号隔开</param>
        /// <returns></returns>
        public override string GetPageSql(string selectSql, string orderSql, int startPageIndex, int resultsPerPage, string otherSql = "")
        {
            return null;//todo 留给志同道合的朋友写吧
        }
        /// <summary>
        /// oracle版。获取前几笔数据的sql
        /// </summary>
        /// <param name="selectSql">主体sql. e.g. select * from Ta A Inner Join Tb B On A.Id=B.UserId where A.Id=100 order by A.Id</param>
        /// <param name="top"></param>
        /// <returns></returns>
        public override string GetTopSql(string selectSql, int top)
        {
            return null;//todo 留给志同道合的朋友写吧
        }
        /// <summary>
        /// oracle版。执行sql前对sql的处理事件。可以检查sql安全性，也可以按不同数据库sql写法做调整。
        /// </summary>
        public override string CheckSql(string strSql)
        {
            //sqlserver是用@，oracle的参数是用冒号
            //oracle语法不支持;结尾
            return strSql.Replace('@', ':').TrimEnd(';', ' ');//如果你们项目确定是用一种数据库写法，以后不会变，则直接return strSql;

            //if (如果有可疑sql(oracle版)) 则记录日志并抛异常
        }
        #endregion
    }
}
